/**
 *  amoobject - C++ simple, safe and fast signal/slot mechanism implementation.
 *
 *  Copyright (C) 2011-2012 Abdeslam MOKRANI (abdesmok@gmail.com).
 *  All rights reserved.
 *
 *  This file is part of amo project.
 *
 *  This file may be freely redistributed and/or modified under ther terms
 *  of the GNU Lesser General Public (LGPL) License as published by the
 *  Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This file is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this file. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef __AMO_OBJECT__
#define __AMO_OBJECT__

#include <vector>
#include <algorithm>
#include <cassert>

#ifndef AMO_NO_OBJECT_NAME
#include <string>
#endif

namespace amo {

class Signal;

typedef std::vector<Signal *> Signals;

class Object {
public:
   Object() :
      _sender(NULL),
      _signal(NULL),
      _deleted(false)
   {
   }
   
   virtual ~Object();
   
   Object *sender() const;
   Signal *signal() const;
   const Signals &incomingSignals() const;
   
   void disconnect();
   
   void deleteThis();
   
   #ifndef AMO_NO_OBJECT_NAME
   void setName(const std::string &name);
   const std::string &name() const;
   #endif
   
private:
   Object(const Object &);
   void operator=(const Object &);
   
   mutable Object *_sender;
   mutable Signal *_signal;
   mutable Signals _incomingSignals;
   bool _deleted;
   
   #ifndef AMO_NO_OBJECT_NAME
   std::string _name;
   #endif
   
   friend class Signal;
};

class Signal {
protected:

   //
   // Nested types
   //
   
   typedef void (Object::*GenericSignature)();
   
   class Connection {
   public:
      Connection(Object *receiver, GenericSignature slot, unsigned int argsCount) :
         receiver(receiver),
         slot(slot),
         argsCount(argsCount)
      {
      }
      
      Object *receiver;
      GenericSignature slot;
      unsigned int argsCount : 3;
   };
   
   typedef std::vector<Connection> Connections;
   
   class GuardData {
   public:
      int call;
   };
   
   class Data {
   public:
      Connections connections;
      GuardData *guard;
   };
   
   //
   // Construction
   //
   
   Signal() :
      _data(NULL)
   {
   }
   
   ~Signal() {
      if (! this->_data)
         return;
         
      for (Connections::iterator i = this->_data->connections.begin(); i != this->_data->connections.end(); ++i) {
         if (! i->receiver) {
            // Object disconnected in previous callback
            continue;
         }
         
         unrefFromObject(i->receiver);
      }
      
      if (this->_data->guard) {
         // this signal deletion occurs in callback
         this->_data->guard->call = -1;
      }
      
      delete this->_data;
   }
   
   //
   // Connection
   //
   
   void connect(Object *receiver, GenericSignature slot, unsigned int argsCount) {
      if (! receiver || ! slot)
         return;
         
      ref();
      
      if (std::find(
         receiver->_incomingSignals.begin(),
         receiver->_incomingSignals.end(),
         this) == receiver->_incomingSignals.end())
      {
         receiver->_incomingSignals.push_back(this);
      }
      
      this->_data->connections.push_back(Connection(receiver, slot, argsCount));
   }
   
   void connectOnce(Object *receiver, GenericSignature slot, unsigned int argsCount) {
      if (! receiver || ! slot)
         return;
         
      ref();
      
      for (Connections::iterator i = this->_data->connections.begin(); i != this->_data->connections.end(); ++i) {
         if (i->receiver == receiver && i->slot == slot)
            return;
      }
      
      connect(receiver, slot, argsCount);
   }
   
   void disconnect(Object *receiver, GenericSignature slot, unsigned int argsCount) {
      if (! this->_data)
         return;
         
      bool unref = true;
      
      for (Connections::iterator i = this->_data->connections.begin(); i != this->_data->connections.end(); ) {
         if (i->receiver == receiver) {
            if (i->slot == slot && i->argsCount == argsCount) {
               if (this->_data->guard) {
                  // disconnect in callback
                  i->receiver = NULL;
                  ++i;
               }
               else {
                  i = this->_data->connections.erase(i);
               }
            }
            else {
               unref = false;
               ++i;
            }
         }
         else {
            ++i;
         }
      }
      
      if (unref)
         unrefFromObject(receiver);
   }
   
   void disconnect(Object *receiver, bool unref = true) {
      if (! this->_data)
         return;
         
      for (Connections::iterator i = this->_data->connections.begin(); i != this->_data->connections.end(); ) {
         if (i->receiver == receiver) {
            if (this->_data->guard) {
               // disconnect in callback
               i->receiver = NULL;
               ++i;
            }
            else {
               i = this->_data->connections.erase(i);
            }
         }
         else {
            ++i;
         }
      }
      
      if (unref) {
         unrefFromObject(receiver);
      }
   }
   
   void disconnect() {
      if (! this->_data)
         return;
         
      for (Connections::iterator i = this->_data->connections.begin(); i != this->_data->connections.end(); ++i) {
         if (i->receiver)
            unrefFromObject(i->receiver);
      }
      
      if (this->_data->guard) {
         // disconnect in callback
         for (Connections::iterator i = this->_data->connections.begin(); i != this->_data->connections.end(); ++i) {
            i->receiver = NULL;
         }
      }
      else {
         this->_data->connections.clear();
      }
   }
   
   bool isConnected(Object *receiver, GenericSignature slot, unsigned int /*argsCount*/) const {
      if (this->_data) {
         for (Connections::iterator i = this->_data->connections.begin(); i != this->_data->connections.end(); ++i) {
            if (i->receiver == receiver && i->slot == slot)
               return true;
         }
      }
      
      return false;
   }
   
   bool isConnected(Object *receiver) const {
      if (this->_data) {
         for (Connections::iterator i = this->_data->connections.begin(); i != this->_data->connections.end(); ++i) {
            if (i->receiver == receiver)
               return true;
         }
      }
      
      return false;
   }
   
   bool isConnected() const {
      if (this->_data) {
         for (Connections::iterator i = this->_data->connections.begin(); i != this->_data->connections.end(); ++i) {
            if (i->receiver)
               return true;
         }
      }
      
      return false;
   }
   
   //
   // Emission
   //
   
   class Guard {
   public:
      GuardData *guard, *&guardRef;
      int call;
      
      Guard(GuardData *&guardRef) :
         guardRef(guardRef)
      {
         if (! guardRef) {
            guard = guardRef = new GuardData;
            call = guard->call = 0;
         }
         else {
            // Recursive call
            guard = guardRef;
            call = ++guard->call;
         }
      };
      
      ~Guard() {
         if (call == 0) {
            if (guard->call >= 0) {
               guardRef = NULL;
            }
            delete guard;
         }
      }
      
      bool outdated() const {
         return guard->call < 0 || call < guard->call;
      }
      
   private:
      Guard(const Guard &);
      void operator=(const Guard &);
   };
   
   template<typename A1, typename A2, typename A3, typename A4, typename A5, typename A6>
   void emit(Object *sender, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) {
      if (this->_data && ! this->_data->connections.empty()) {
         Guard guard(this->_data->guard);
         
         int index = 0;
         
         do {
            Connection *connection = &this->_data->connections.at(index);
            
            if (connection->receiver) {
               connection->receiver->_sender = sender;
               connection->receiver->_signal = this;
               
               switch (connection->argsCount) {
               case 0:
                  (connection->receiver->*((void (Object::*)()) connection->slot))();
                  break;
               case 1:
                  (connection->receiver->*((void (Object::*)(A1)) connection->slot))(a1);
                  break;
               case 2:
                  (connection->receiver->*((void (Object::*)(A1, A2)) connection->slot))(a1, a2);
                  break;
               case 3:
                  (connection->receiver->*((void (Object::*)(A1, A2, A3)) connection->slot))(a1, a2, a3);
                  break;
               case 4:
                  (connection->receiver->*((void (Object::*)(A1, A2, A3, A4)) connection->slot))(a1, a2, a3, a4);
                  break;
               case 5:
                  (connection->receiver->*((void (Object::*)(A1, A2, A3, A4, A5)) connection->slot))(a1, a2, a3, a4, a5);
                  break;
               case 6:
                  (connection->receiver->*((void (Object::*)(A1, A2, A3, A4, A5, A6)) connection->slot))(a1, a2, a3, a4, a5, a6);
                  break;
               }
               
               if (guard.outdated()) {
                  // Signal outdated, return immediately
                  return;
               }
               
               // this->_data->connections might be resized during callback, update connection pointer
               connection = &this->_data->connections.at(index);
               
               if (connection->receiver) {
                  connection->receiver->_sender = NULL;
                  connection->receiver->_signal = NULL;
                  
                  ++index;
               }
               else {
                  // Object disconnected in callback
                  this->_data->connections.erase(this->_data->connections.begin() + index);
               }
            }
            else {
               // Object disconnected in previous callback
               this->_data->connections.erase(this->_data->connections.begin() + index);
            }
         }
         while (index < int(this->_data->connections.size()));
      }
   }
   
   //
   // Reference
   //
   
   void unrefFromObject(Object *receiver) {
      for (
         std::vector<Signal *>::iterator ci = receiver->_incomingSignals.begin();
         ci != receiver->_incomingSignals.end();
      )
      {
         if (*ci == this)
            ci = receiver->_incomingSignals.erase(ci);
         else
            ++ci;
      }
   }
   
   void ref() {
      if (! this->_data) {
         this->_data = new Data;
         this->_data->guard = NULL;
      }
   }
   
   //
   // Data
   //
   
   Data *_data;
   
private:
   Signal(const Signal &);
   void operator=(const Signal &);
   friend class Object;
};

inline Object::~Object() {
   assert(! this->_deleted && "Object multiple deletion detected");
   
   this->_deleted = true;
   
   // disconnect all incoming signals
   for (Signals::iterator i = this->_incomingSignals.begin(); i != this->_incomingSignals.end(); ++i) {
      (*i)->disconnect(this, false);
   }
}

inline Object *Object::sender() const {
   return this->_sender;
}

inline Signal *Object::signal() const {
   return this->_signal;
}

const Signals &Object::incomingSignals() const {
   return this->_incomingSignals;
}

inline void Object::disconnect() {
   // disconnect all incoming signals
   
   for (Signals::iterator i = this->_incomingSignals.begin(); i != this->_incomingSignals.end(); ++i) {
      (*i)->disconnect(this, false);
   }
   
   this->_incomingSignals.clear();
}

inline void Object::deleteThis() {
   // delete slot, use with caution
   
   delete this;
}

inline void Object::setName(const std::string &name) {
    this->_name = name;
}

inline const std::string &Object::name() const {
    return this->_name;
}

} // namespace amo

// Visual studio work arround
#define AMO_VC_EVAL(...) __VA_ARGS__

// Visual studio 2005 bug workarround: add many dummy commas...
// If __VA_ARGS__ is empty then the buggy preprocessor will remove these dummy commas first
#if defined(_MSC_VER) && (_MSC_VER <= 1400)
#define AMO_VS_2005_PRESERVE_PREV_COMMAS +1,+1,+1,+1,+1,+1,+1;
#else
#define AMO_VS_2005_PRESERVE_PREV_COMMAS
#endif

#define AMO_ARG_COUNT(...) \
   AMO_VC_EVAL(AMO_ARG_COUNT_IMPL_A(AMO_ARG_COUNT_IMPL_ZERO##__VA_ARGS__, 0, 6, 5, 4, 3, 2, 1, 0))
#define AMO_ARG_COUNT_IMPL_ZERO __a1, __a2, __a3, __a4, __a5, __a6, __a7
#define AMO_ARG_COUNT_IMPL_A(...) AMO_VC_EVAL(AMO_ARG_COUNT_IMPL_B(__VA_ARGS__))
#define AMO_ARG_COUNT_IMPL_B(__a1, __a2, __a3, __a4, __a5, __a6, __a7, __count, ...) __count

#define AMO_ARG_REDUCE(__count, ...) AMO_VC_EVAL(AMO_ARG_REDUCE_IMPL(__count, __VA_ARGS__))
#define AMO_ARG_REDUCE_IMPL(__count, ...) AMO_VC_EVAL(AMO_ARG_REDUCE_IMPL_##__count(__VA_ARGS__))
#define AMO_ARG_REDUCE_IMPL_0(...)
#define AMO_ARG_REDUCE_IMPL_1(__a1, ...) __a1
#define AMO_ARG_REDUCE_IMPL_2(__a1, __a2, ...) __a1, __a2
#define AMO_ARG_REDUCE_IMPL_3(__a1, __a2, __a3, ...) __a1, __a2, __a3
#define AMO_ARG_REDUCE_IMPL_4(__a1, __a2, __a3, __a4, ...) __a1, __a2, __a3, __a4
#define AMO_ARG_REDUCE_IMPL_5(__a1, __a2, __a3, __a4, __a5, ...) __a1, __a2, __a3, __a4, __a5
#define AMO_ARG_REDUCE_IMPL_6(__a1, __a2, __a3, __a4, __a5, __a6, ...) __a1, __a2, __a3, __a4, __a5, __a6

#define AMO_ARG_ADD_VARS(...) \
   AMO_VC_EVAL(AMO_ARG_ADD_VARS_IMPL(AMO_ARG_COUNT(__VA_ARGS__), __VA_ARGS__, __a1, __a2, __a3, __a4, __a5))
#define AMO_ARG_ADD_VARS_IMPL(__count, __a1, __a2, __a3, __a4, __a5, __a6, ...) \
   AMO_ARG_REDUCE(__count, __a1 a1, __a2 a2, __a3 a3, __a4 a4, __a5 a5, __a6 a6)
   
#define AMO_SIGNAL_CONNECT(...) AMO_VC_EVAL(AMO_SIGNAL_CONNECT_IMPL_A(AMO_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)) \
   void disconnect(amo::Object *receiver) { amo::Signal::disconnect(static_cast<amo::Object *>(receiver)); } \
   void disconnect() { amo::Signal::disconnect(); } \
   bool isConnected(amo::Object *receiver) const { return amo::Signal::isConnected(receiver); } \
   bool isConnected() const { return amo::Signal::isConnected(); }
#define AMO_SIGNAL_CONNECT_IMPL_A(__count, ...) AMO_VC_EVAL(AMO_SIGNAL_CONNECT_IMPL_B(__count, __VA_ARGS__))
#define AMO_SIGNAL_CONNECT_IMPL_B(__count, ...) AMO_SIGNAL_CONNECT_##__count(AMO_ARG_REDUCE(__count, __VA_ARGS__))
#define AMO_SIGNAL_CONNECT_0(...) AMO_SIGNAL_CONNECT_STRICT(0, __VA_ARGS__)
#define AMO_SIGNAL_CONNECT_1(...) AMO_SIGNAL_CONNECT_STRICT(1, __VA_ARGS__) AMO_SIGNAL_CONNECT_0(AMO_ARG_REDUCE(0, __VA_ARGS__))
#define AMO_SIGNAL_CONNECT_2(...) AMO_SIGNAL_CONNECT_STRICT(2, __VA_ARGS__) AMO_SIGNAL_CONNECT_1(AMO_ARG_REDUCE(1, __VA_ARGS__))
#define AMO_SIGNAL_CONNECT_3(...) AMO_SIGNAL_CONNECT_STRICT(3, __VA_ARGS__) AMO_SIGNAL_CONNECT_2(AMO_ARG_REDUCE(2, __VA_ARGS__))
#define AMO_SIGNAL_CONNECT_4(...) AMO_SIGNAL_CONNECT_STRICT(4, __VA_ARGS__) AMO_SIGNAL_CONNECT_3(AMO_ARG_REDUCE(3, __VA_ARGS__))
#define AMO_SIGNAL_CONNECT_5(...) AMO_SIGNAL_CONNECT_STRICT(5, __VA_ARGS__) AMO_SIGNAL_CONNECT_4(AMO_ARG_REDUCE(4, __VA_ARGS__))
#define AMO_SIGNAL_CONNECT_6(...) AMO_SIGNAL_CONNECT_STRICT(6, __VA_ARGS__) AMO_SIGNAL_CONNECT_5(AMO_ARG_REDUCE(5, __VA_ARGS__))

#define AMO_SIGNAL_CONNECT_STRICT(__count, ...) \
   AMO_VC_EVAL(AMO_SIGNAL_CONNECT_STRICT_IMPL(__count, void, connect, (__VA_ARGS__), )) \
   AMO_VC_EVAL(AMO_SIGNAL_CONNECT_STRICT_IMPL(__count, void, connectOnce, (__VA_ARGS__), )) \
   AMO_VC_EVAL(AMO_SIGNAL_CONNECT_STRICT_IMPL(__count, void, disconnect, (__VA_ARGS__), )) \
   AMO_VC_EVAL(AMO_SIGNAL_CONNECT_STRICT_IMPL(__count, bool, isConnected, (__VA_ARGS__), const))
#define AMO_SIGNAL_CONNECT_STRICT_IMPL(__count, __return_type, __method, __args, __constantness) \
   template <typename R, typename T1, typename T2> \
   __return_type __method(T1 *receiver, R (T2::*slot)__args) __constantness { \
      return amo::Signal::__method( \
         static_cast<amo::Object *>(static_cast<T2 *>(receiver)), \
         reinterpret_cast<GenericSignature>(static_cast<R (amo::Object::*)__args>(slot)), \
         __count \
      ); \
      AMO_VS_2005_PRESERVE_PREV_COMMAS \
   }
   
#define AMO_SIGNAL_EMIT(...) \
   AMO_SIGNAL_EMIT_IMPL(AMO_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)
#define AMO_SIGNAL_EMIT_IMPL(__count, ...) \
   void emit(amo::Object *object AMO_SIGNAL_EMIT_ADD_VARS(__count, __VA_ARGS__)) { \
      amo::Signal::emit<__VA_ARGS__>( \
         object, AMO_SIGNAL_EMIT_VARS(__count) \
      ); \
      AMO_VS_2005_PRESERVE_PREV_COMMAS \
   }
   
#define AMO_SIGNAL_EMIT_ADD_VARS(__count, ...) AMO_SIGNAL_EMIT_ADD_VARS_IMPL(__count, __VA_ARGS__)
#define AMO_SIGNAL_EMIT_ADD_VARS_IMPL(__count, ...) AMO_VC_EVAL(AMO_SIGNAL_EMIT_ADD_VARS_IMPL_##__count(__VA_ARGS__))
#define AMO_SIGNAL_EMIT_ADD_VARS_IMPL_0(...) AMO_ARG_ADD_VARS(__VA_ARGS__)
#define AMO_SIGNAL_EMIT_ADD_VARS_IMPL_1(...) , AMO_ARG_ADD_VARS(__VA_ARGS__)
#define AMO_SIGNAL_EMIT_ADD_VARS_IMPL_2(...) , AMO_ARG_ADD_VARS(__VA_ARGS__)
#define AMO_SIGNAL_EMIT_ADD_VARS_IMPL_3(...) , AMO_ARG_ADD_VARS(__VA_ARGS__)
#define AMO_SIGNAL_EMIT_ADD_VARS_IMPL_4(...) , AMO_ARG_ADD_VARS(__VA_ARGS__)
#define AMO_SIGNAL_EMIT_ADD_VARS_IMPL_5(...) , AMO_ARG_ADD_VARS(__VA_ARGS__)
#define AMO_SIGNAL_EMIT_ADD_VARS_IMPL_6(...) , AMO_ARG_ADD_VARS(__VA_ARGS__)

#define AMO_SIGNAL_EMIT_VARS(__count) AMO_VC_EVAL(AMO_SIGNAL_EMIT_VARS_IMPL(__count))
#define AMO_SIGNAL_EMIT_VARS_IMPL(__count) AMO_VC_EVAL(AMO_SIGNAL_EMIT_VARS_IMPL_##__count)
#define AMO_SIGNAL_EMIT_VARS_IMPL_0  0,  0,  0,  0,  0,  0
#define AMO_SIGNAL_EMIT_VARS_IMPL_1 a1,  0,  0,  0,  0,  0
#define AMO_SIGNAL_EMIT_VARS_IMPL_2 a1, a2,  0,  0,  0,  0
#define AMO_SIGNAL_EMIT_VARS_IMPL_3 a1, a2, a3,  0,  0,  0
#define AMO_SIGNAL_EMIT_VARS_IMPL_4 a1, a2, a3, a4,  0,  0
#define AMO_SIGNAL_EMIT_VARS_IMPL_5 a1, a2, a3, a4, a5,  0
#define AMO_SIGNAL_EMIT_VARS_IMPL_6 a1, a2, a3, a4, a5, a6

#define AMO_SIGNAL_CLASS_NAME(__name) Signal##__name

#define AMO_SIGNAL_CLASS_BODY(__name, ...) \
{ \
public: \
   AMO_SIGNAL_CLASS_NAME(__name)() : amo::Signal() {} \
   AMO_SIGNAL_CONNECT(__VA_ARGS__) \
   AMO_SIGNAL_EMIT(__VA_ARGS__) \
}

#define AMO_SIGNAL_CLASS(__name, ...) \
   class AMO_SIGNAL_CLASS_NAME(__name) : public amo::Signal \
   AMO_SIGNAL_CLASS_BODY(__name, __VA_ARGS__);
   
#define AMO_SIGNAL_EMIT_SLOT(__name, ...) \
   void emit_##__name(AMO_ARG_ADD_VARS(__VA_ARGS__)) { \
      this->_signal##__name.emit( \
         this AMO_SIGNAL_EMIT_SLOT_VARS(AMO_ARG_COUNT(__VA_ARGS__)) \
      ); \
   }
   
#define AMO_SIGNAL_EMIT_SLOT_VARS(__count) AMO_VC_EVAL(AMO_SIGNAL_EMIT_SLOT_VARS_IMPL(__count))
#define AMO_SIGNAL_EMIT_SLOT_VARS_IMPL(__count) AMO_VC_EVAL(AMO_SIGNAL_EMIT_SLOT_VARS_IMPL_##__count)
#define AMO_SIGNAL_EMIT_SLOT_VARS_IMPL_0
#define AMO_SIGNAL_EMIT_SLOT_VARS_IMPL_1 , a1
#define AMO_SIGNAL_EMIT_SLOT_VARS_IMPL_2 , a1, a2
#define AMO_SIGNAL_EMIT_SLOT_VARS_IMPL_3 , a1, a2, a3
#define AMO_SIGNAL_EMIT_SLOT_VARS_IMPL_4 , a1, a2, a3, a4
#define AMO_SIGNAL_EMIT_SLOT_VARS_IMPL_5 , a1, a2, a3, a4, a5
#define AMO_SIGNAL_EMIT_SLOT_VARS_IMPL_6 , a1, a2, a3, a4, a5, a6

#define AMO_SIGNAL(__name, ...) \
   AMO_SIGNAL_CLASS(__name, __VA_ARGS__) \
   AMO_SIGNAL_CLASS_NAME(__name) _signal##__name; \
   AMO_SIGNAL_CLASS_NAME(__name) &__name() { \
      return this->_signal##__name; \
   } \
   AMO_SIGNAL_EMIT_SLOT(__name, __VA_ARGS__)
   
#endif // __AMO_OBJECT__

